﻿//Manimaran

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;



namespace Tzync
{
    public class Sync
    {
        private const String HardDiskFolderName = "Files";

        private List<Difference> differences;
        private List<TreeNode> arrNodes;
        private Stack<Difference> folderStack;
        private SystemType systemName;
        private String storeFilesDir;
        private bool firstSyncGuest;
        private SafeSync safeSync;
        private Queue<String> fileQueue; // a queue to delete files in Hard Disk. For safe sync.
        private FileOperations fileOps;
        private FolderOperations folderOps;
        
        /// <summary>
        /// Constructor called for first time and second syncing
        /// </summary>
        /// <param name="arrNodes">contains the metadata</param>
        /// <param name="secondTime">check secondTime is true</param>
        /// <param name="st">host or guest</param>
        /// <param name="profilePath"></param>
        public Sync(List<TreeNode> arrNodes, bool firstSyncGuest, SystemType st, String profilePath, SafeSync safeSync)
        {
            this.arrNodes = arrNodes;
            this.firstSyncGuest = firstSyncGuest;
            systemName = st;
            
            storeFilesDir = profilePath;
            storeFilesDir = Path.Combine(storeFilesDir, HardDiskFolderName);
            SetupFolder();

            this.safeSync = safeSync;
            fileQueue = new Queue<String>();

            fileOps = new FileOperations(storeFilesDir, safeSync);
            folderOps = new FolderOperations(storeFilesDir, safeSync);
        }

        /// <summary>
        /// Called for normal syncing
        /// </summary>
        /// <param name="differences">Files folders to change</param>
        /// <param name="arrNodes">Metadata</param>
        /// <param name="systemName">host or guest</param>
        /// <param name="profilePath">the profile's path</param>
        public Sync(List<Difference> differences, List<TreeNode> arrNodes, SystemType systemName, String profilePath, SafeSync safeSync)
        {
            this.differences = differences;
            this.arrNodes = arrNodes;
            this.systemName = systemName;
            folderStack = new Stack<Difference>();            

            storeFilesDir = profilePath;
            storeFilesDir = Path.Combine(storeFilesDir, HardDiskFolderName);
            SetupFolder();

            this.safeSync = safeSync;
            fileQueue = new Queue<String>();

            fileOps = new FileOperations(storeFilesDir, safeSync);
            folderOps = new FolderOperations(storeFilesDir, safeSync);            
        }

        // Create the folder in the hard disk
        private void SetupFolder()
        {
            try
            {
                //Creating the directory
                Directory.CreateDirectory(storeFilesDir);
            }
            catch (DirectoryNotFoundException)
            {
                throw new DirectoryNotFoundException(storeFilesDir, ActionType.Sync);
            }
        }

        /// <summary>
        /// Calls either file or folder reconcile method
        /// </summary>
        public void Reconcile()
        {
            long fileSize = ComputeFileSizeToTransfer();
            bool hasConflictsAndDirectionIsNone = false;

            if (fileSize != 0)
            {
                bool hardDiskSpaceEnough = CheckDiskSpace(fileSize);

                if (!hardDiskSpaceEnough) throw new DiskSpaceNotEnoughException(GlobalVariables.ProgramPath, ActionType.Sync);
            }
            
            foreach (Difference diff in differences)
            {
                if (diff.GetItemType() == Type.File)
                {
                    if (diff.GetPairType().First != DirectionType.None)
                    {
                        ReconcileFile(diff);
                    }
                    else
                    {
                        hasConflictsAndDirectionIsNone = true;
                    }
                }
                else if (diff.GetItemType() == Type.Folder)
                {
                    ReconcileFolder(diff);
                }
            }
            fileOps.DeleteFilesinHD();
            if (!hasConflictsAndDirectionIsNone)
                folderOps.UpdateFoldersTimeandSize(arrNodes, systemName);
            
            // Signal safe sync done
            safeSync.Append(SafeSyncType.Finished, SafeSyncType.Finished.ToString());
        }

        /// <summary>
        /// Overloaded method of reconcile. To cater for handling of directory if there are conflicts.
        /// Basically it is to cater for modification of file at one system and deletion of folder at the 
        /// other system.
        /// </summary>
        /// <param name="previousDifferences">Already resolved differences but used to retrieve folder info</param>
        public void Reconcile(List<Difference> previousDifferences)
        {
            long fileSize = ComputeFileSizeToTransfer();
            bool hasConflictsAndDirectionIsNone = false;

            if (fileSize != 0)
            {
                bool hardDiskSpaceEnough = CheckDiskSpace(fileSize);

                if (!hardDiskSpaceEnough) throw new DiskSpaceNotEnoughException(GlobalVariables.ProgramPath, ActionType.Sync);
            }

            foreach (Difference diff in previousDifferences)
            {
                if (diff.GetItemType() == Type.Folder)
                {
                    folderOps.PushToStack(diff);
                }
            }

            foreach (Difference diff in differences)
            {
                if (diff.GetItemType() == Type.File)
                {
                    if (diff.GetPairType().First != DirectionType.None)
                    {
                        ReconcileFile(diff);
                    }
                    else
                    {
                        hasConflictsAndDirectionIsNone = true;
                    }
                }
                else if (diff.GetItemType() == Type.Folder)
                {
                    ReconcileFolder(diff);
                }
            }
            fileOps.DeleteFilesinHD();
            if (!hasConflictsAndDirectionIsNone)
                folderOps.UpdateFoldersTimeandSize(arrNodes, systemName);
            // Signal safe sync done
            safeSync.Append(SafeSyncType.Finished, SafeSyncType.Finished.ToString());
        }

        // Does syncing based on the dfferent sync operations for file. Calls the FileOperations class
        private void ReconcileFile(Difference diff)
        {            
            TreeNode tNode;

            tNode = diff.GetPartnerFile();

            try
            {

                #region Creation
                if (diff.GetPairType().Second == OperationType.Creation)
                    arrNodes[tNode.GetId()] = fileOps.Creation(tNode, diff, systemName);
                #endregion

                #region Deletion
                else if (diff.GetPairType().Second == OperationType.Deletion)
                {
                    arrNodes[tNode.GetId()] = fileOps.Deletion(tNode, diff, systemName);
                }
                #endregion

                #region Modification
                else if (diff.GetPairType().Second == OperationType.Modification)
                {
                    arrNodes[tNode.GetId()] = fileOps.Modification(tNode, diff, systemName);
                }
                #endregion

                #region Modification Modification Conflict
                else if (diff.GetPairType().Second == OperationType.ModModConflict)
                {
                    arrNodes[tNode.GetId()] = fileOps.ModMod(tNode, diff, systemName);
                }
                #endregion

                #region Modification Deletion Conflict
                else if (diff.GetPairType().Second == OperationType.ModDeleteConflict)
                {
                    arrNodes[tNode.GetId()] = fileOps.ModDelete(tNode, diff, systemName);
                }
                #endregion

                #region Creation Creation Conflict
                else if (diff.GetPairType().Second == OperationType.CreationCreationConflict)
                {
                    arrNodes[tNode.GetId()] = fileOps.CreationCreation(tNode, diff, systemName);
                }
                #endregion

                #region Deletion Deletion Conflict
                else if (diff.GetPairType().Second == OperationType.DeletionDeletionConflict)
                {
                    arrNodes[tNode.GetId()] = fileOps.DeletionDeletion(tNode, diff, systemName);
                }
                #endregion
            }
            catch (UnauthorizedAccessException) {
                if (systemName == SystemType.Host)
                    throw new UnauthorizedAccessException(tNode.GetFullPathHost(), ActionType.Sync);
                else if (systemName == SystemType.Guest)
                    throw new UnauthorizedAccessException(tNode.GetFullPathGuest(), ActionType.Sync);
            }
            catch (FileNotFoundException)
            {
                if (systemName == SystemType.Host)
                    throw new FileNotFoundException(tNode.GetFullPathHost(), ActionType.Sync);
                else if (systemName == SystemType.Guest)
                    throw new FileNotFoundException(tNode.GetFullPathGuest(), ActionType.Sync);
            }
            catch (PathTooLongException)
            {
                if (systemName == SystemType.Host)
                    throw new PathTooLongException(tNode.GetFullPathHost(), ActionType.Sync);
                else if (systemName == SystemType.Guest)
                    throw new PathTooLongException(tNode.GetFullPathGuest(), ActionType.Sync);
            }
        }

        // Does syncing based on the dfferent sync operations for folder. Calls the FolderOperations class
        private void ReconcileFolder(Difference diff)
        {
            TreeNode tNode = diff.GetPartnerFolder();

            folderOps.PushToStack(diff);

            try
            {

            #region creation
            if (diff.GetPairType().Second == OperationType.Creation)
            {
                arrNodes[tNode.GetId()] = folderOps.Creation(diff, systemName);

            }
            #endregion

            #region Deletion
            else if (diff.GetPairType().Second == OperationType.Deletion)
            {
                arrNodes[tNode.GetId()] = folderOps.Deletion(diff, systemName);
            }
            #endregion

            #region Creation Creation Conflict
            else if (diff.GetPairType().Second == OperationType.CreationCreationConflict)
            {
                arrNodes[tNode.GetId()] = folderOps.CreationCreation(diff, systemName);
            }
            #endregion

            #region Deletion Deletion Conflict
            else if (diff.GetPairType().Second == OperationType.DeletionDeletionConflict)
            {
                arrNodes[tNode.GetId()] = folderOps.DeletionDeletion(diff, systemName);
            }
            #endregion

            }
            catch (UnauthorizedAccessException)
            {
                if (systemName == SystemType.Host)
                    throw new UnauthorizedAccessException(tNode.GetFullPathHost(), ActionType.Sync);
                else if (systemName == SystemType.Guest)
                    throw new UnauthorizedAccessException(tNode.GetFullPathGuest(), ActionType.Sync);
            }
            catch (DirectoryNotFoundException)
            {
                if (systemName == SystemType.Host)
                    throw new DirectoryNotFoundException(tNode.GetFullPathHost(), ActionType.Sync);
                else if (systemName == SystemType.Guest)
                    throw new DirectoryNotFoundException(tNode.GetFullPathGuest(), ActionType.Sync);
            }
            catch (PathTooLongException)
            {
                if (systemName == SystemType.Host)
                    throw new PathTooLongException(tNode.GetFullPathHost(), ActionType.Sync);
                else if (systemName == SystemType.Guest)
                    throw new PathTooLongException(tNode.GetFullPathGuest(), ActionType.Sync);
            }
        }

        /// <summary>
        /// For first time syncing. Copy files to Hard disk
        /// </summary>
        /// <param name="source">System A's path</param>
        public void CopyFilesToHD(DirectoryInfo source)
        {
            long fileSize = Utility.CalculateDirectorySize(source);
            bool hardDiskSpaceEnough = CheckDiskSpace(fileSize);

            if (!hardDiskSpaceEnough) throw new DiskSpaceNotEnoughException(GlobalVariables.ProgramPath, ActionType.Sync);

            DirectoryInfo destination = new DirectoryInfo(storeFilesDir);

            try
            {
                Directory.CreateDirectory(destination.FullName);
            }
            catch (DirectoryNotFoundException)
            {
               throw new DirectoryNotFoundException(destination.FullName, ActionType.Sync);
            }

            CopyDirectory(source, destination);
        }

        public void DeleteFilesForSecondTime()
        {
            DirectoryInfo source = new DirectoryInfo(storeFilesDir);
            FileInfo[] files = source.GetFiles();
            bool found = false;
            foreach (FileInfo file in files)
            {
                if (File.Exists(file.FullName))
                {
                    try
                    {
                        if (differences != null)
                        {
                            foreach (Difference diff in differences)
                            {
                                if (diff.GetItemType() == Type.File)
                                {
                                    if (file.Name.Equals(Utility.StripString(diff.GetPartnerFile().GetFullPathHost())) || file.Name.Equals(Utility.StripString(diff.GetPartnerFile().GetFullPathGuest())))
                                    {
                                        if (diff.GetPairType().First == DirectionType.HDToSystem)
                                        {
                                            file.Delete();
                                        }
                                        found = true; // this means file is found but direction is SystemToHD and so will not be deleted
                                        break;
                                    }
                                }
                            }
                            if (!found) file.Delete(); // file is not in differences list. So will be deleted.
                            found = false;
                        }
                        else // no differences. So just delete all files
                        {
                            file.Delete();
                        }
                    }
                    catch (UnauthorizedAccessException)
                    {
                        throw new UnauthorizedAccessException(file.FullName, ActionType.Sync);
                    }
                    catch (FileNotFoundException)
                    {
                        throw new UnauthorizedAccessException(file.FullName, ActionType.Sync);
                    }
                }
            }
        }

        /// <summary>
        /// Update the meta data
        /// </summary>
        /// <param name="rootAddress">Source or target Address</param>
        public void UpdateArrNodesForSecondTime(String rootAddress)
        {
            String dtp;
            TreeNode tParentNode = arrNodes[0];
            int count = 0;
            DirectoryInfo d, rootB = new DirectoryInfo(rootAddress);
            FileInfo f;

            TreeOperation t = new TreeOperation(arrNodes, firstSyncGuest, systemName);
            t.UpdateRootNode(rootB, arrNodes[0]);

            foreach (TreeNode tNode in arrNodes)
            {
                tParentNode = arrNodes[tNode.GetParent()];
                if (count != 0)
                {
                    tNode.SetFullPathGuest(Path.Combine(tParentNode.GetFullPathGuest(), tNode.GetName()));
                }
                if (tNode.GetNodeType() == Type.File)
                {
                    f = new FileInfo(tNode.GetFullPathGuest());
                    dtp = Utility.ParseDateTime(f.LastWriteTime);
                    tNode.SetSyncGuest(dtp);
                }
                else
                {
                    d = new DirectoryInfo(tNode.GetFullPathGuest());
                    dtp = Utility.ParseDateTime(d.LastWriteTime);
                    tNode.SetSyncGuest(dtp);
                }
                tNode.SetSizeGuest(tNode.GetSizeHost());
                // Version A might be modified before second time. So set version B to 1,0 then call analyze in Facade class
                tNode.GetVersionGuest().First = 1;
                tNode.GetVersionGuest().Second = 0;

                count++;
            }
        }

        // Copy contents of the directory
        private void CopyDirectory(DirectoryInfo source, DirectoryInfo destination)
        {
            try
            {
                // Copy all files.
                FileInfo[] files = source.GetFiles();
                String filePath = "";
                FileAttributes fileAtts;
                foreach (FileInfo file in files)
                {
                    //filePath = Path.Combine(destination.FullName, file.Name);
                    filePath = Path.Combine(destination.FullName, Utility.StripString(file.FullName));
                    if (!File.Exists(filePath))
                    {
                        fileAtts = File.GetAttributes(file.FullName);
                        File.SetAttributes(file.FullName, fileAtts & ~FileAttributes.ReadOnly);
                        try
                        {
                            file.CopyTo(Path.Combine(@"\\?\UNC", filePath));
                        }
                        catch (UnauthorizedAccessException)
                        {
                            throw new UnauthorizedAccessException(file.FullName, ActionType.Sync);
                        }

                        catch (FileNotFoundException)
                        {
                            throw new UnauthorizedAccessException(file.FullName, ActionType.Sync);
                        }

                        catch (DirectoryNotFoundException)
                        {
                            throw new DirectoryNotFoundException(file.FullName, ActionType.Sync);
                        }
                    }
                }

                // Process subdirectories.
                DirectoryInfo[] dirs = source.GetDirectories();
                foreach (DirectoryInfo dir in dirs)
                {
                    //string destinationDir = Path.Combine(destination.FullName, dir.Name);

                    // Call CopyDirectory() recursively.
                    //CopyDirectory(dir, new DirectoryInfo(destinationDir));
                    CopyDirectory(dir, destination);
                }
            }
            catch (UnauthorizedAccessException)
            {
                throw new UnauthorizedAccessException(source.FullName, ActionType.Sync);
            }
            catch (DirectoryNotFoundException)
            {
                throw new DirectoryNotFoundException(source.FullName, ActionType.Sync);
            }
        }

        /// <summary>
        /// Deletes the contents of the directory
        /// </summary>
        /// <param name="dirInfo"></param>
        public void DeleteDirectory(DirectoryInfo dirInfo)
        {
            // Copy all files.
            FileInfo[] files = dirInfo.GetFiles();
            FileAttributes fileAtts;
            DirectoryInfo dirDest;
            foreach (FileInfo file in files)
            {
                try 
                {
                    fileAtts = File.GetAttributes(file.FullName);
                    File.SetAttributes(file.FullName, fileAtts & ~FileAttributes.ReadOnly);
                    file.Delete();
                }
                catch (UnauthorizedAccessException)
                {
                    throw new UnauthorizedAccessException(file.FullName, ActionType.Sync);
                }

                catch (FileNotFoundException)
                {
                    throw new UnauthorizedAccessException(file.FullName, ActionType.Sync);
                }
            }

            // Process subdirectories.
            DirectoryInfo[] dirs = dirInfo.GetDirectories();
            foreach (DirectoryInfo dir in dirs)
            {
                try
                {
                    string destinationDir = Path.Combine(dirInfo.FullName, dir.Name);
                    dirDest = new DirectoryInfo(destinationDir);

                    // Call CopyDirectory() recursively.
                    DeleteDirectory(dirDest);
                    dirDest.Delete();
                }
                catch (UnauthorizedAccessException)
                {
                    throw new UnauthorizedAccessException(dir.FullName, ActionType.Sync);
                }
                catch (DirectoryNotFoundException)
                {
                    throw new DirectoryNotFoundException(dir.FullName, ActionType.Sync);
                }

            }
        }

        // Method to see if hard disk has enough space
        private bool CheckDiskSpace(long fileSize)
        {
            DriveInfo cDrive = new DriveInfo(storeFilesDir.Substring(0, 2)); // get the Drive. Eg: 'G:'
            long hardDiskSize = cDrive.AvailableFreeSpace;    //available space        
            if (fileSize > hardDiskSize) return false;
            else return true;
        }

        // Check the size of files that will need to be synced to hard disk
        private long ComputeFileSizeToTransfer()
        {
            if (differences == null)
                return 0;

            long fileSize = 0;
            FileInfo f = null;
            foreach (Difference diff in differences)
            {
                if (diff.GetPairType().Second != OperationType.Deletion
                    && diff.GetPairType().Second != OperationType.DeletionDeletionConflict
                    && diff.GetPairType().Second != OperationType.ModDeleteConflict)
                {
                    if (diff.GetItemType() == Type.File)
                    {
                        if (diff.GetPartnerFile() != null && diff.GetPairType().First == DirectionType.SystemToHD)
                        {
                            if (systemName == SystemType.Host)
                            {
                                if (diff.GetPartnerFile().GetFullPathHost().Length != 0)
                                    f = new FileInfo(diff.GetPartnerFile().GetFullPathHost());
                            }
                            else
                            {
                                if (diff.GetPartnerFile().GetFullPathGuest().Length != 0)
                                    f = new FileInfo(diff.GetPartnerFile().GetFullPathGuest()); // problem for firsttime
                            }
                            if (f != null)
                            {
                                fileSize += f.Length;
                                f = null; // set to null just to make sure next file is a fresh copy
                            }
                        }
                    }
                }
            }
            return fileSize;
        }
    }
}
